4.5 Konstruktoren
 
Konstruktoren sind Methoden, die der kontrollierten Initialisierung von Objekten dienen, z.B. um den Feldern die erforderlichen Anfangswerte zuzuweisen, die Verbindung zu einer Datenbank aufzubauen oder eine Datei zu öffnen. Damit werden unzulässige Objektzustände vermieden, wenn einem Objekt nach Abschluss seiner Instanziierung substanzielle Startwerte fehlen.
| Konstruktoren – auch als Initialisierungs- oder Erstellungsfunktionen bezeichnet – sind spezielle Methoden, die bei der Instanziierung einer Klasse automatisch aufgerufen werden.
|
Die Definition der Konstruktoren sieht wie folgt aus:
| Zugriffsmodifizierer Klassenbezeichner([Parameterliste]){*/...*/}
|
Zuerst wird ein Zugriffsmodifizierer angegeben, dahinter der Klassenbezeichner. Optional können Konstruktoren auch eine Parameterliste haben. Konstruktoren haben grundsätzlich keinen Rückgabewert, auch nicht void.
4.5.1 Die Konstruktoren in der Klasse »Circle«
 
Betrachten Sie das Beispiel der Klasse Circle. Wird ein Objekt dieser Klasse mit
| Circle meinKreis;
|
| meinKreis = new Circle();
|
erzeugt, verbergen sich hinter dem Erstellungsprozess zwei Schritte.
|
Es wird der erforderliche Speicher für die Daten des Objekts reserviert. |
|
Es wird ein Konstruktor aufgerufen, der das Objekt initialisiert. |
Ein Blick in die aktuelle Implementierung der Klasse Circle wirft die Frage auf, wo der Konstruktor zu finden ist, der für die Initialisierung verantwortlich ist. Die Antwort ist einfach: Die augenblickliche Klassenimplementierung enthält zwar explizit keinen Konstruktor, er existiert aber dennoch, allerdings implizit. Diesen Konstruktor, der parameterlos ist, bezeichnet man auch als Standardkonstruktor.
| // der implizite Standardkonstruktor der Klasse Circle
|
| public Circle() { }
|
Der Standardkonstruktor ist grundsätzlich immer vorhanden – zumindest solange wir keinen parametrisierten definieren. Davon später mehr.
Um nach der Instanziierung dem Circle-Objekt individuelle Daten zuzuweisen, muss der Benutzer der Klasse jede Eigenschaft einzeln aufrufen, z.B.:
| meinKreis.Radius = 10;
|
| meinKreis.XKoordinate = 20;
|
| meinKreis.YKoordinate = 20;
|
Wäre es nicht sinnvoller, einem Circle-Objekt schon bei dessen Instanziierung den Radius mitzuteilen, vielleicht sogar auch noch optional gleichzeitig die Mittelpunktskoordinaten? Genau diese Aufgabe können entsprechend parametrisierte Konstruktoren übernehmen, denn Konstruktoren dürfen nach nahezu denselben Regeln wie andere Methoden überladen werden.
Das Beispiel der Circle-Klasse wollen wir nun so ergänzen, dass bei der Instanziierung unter drei Erstellungsoptionen ausgewählt werden kann:
|
Ein Circle-Objekt kann wie bisher ohne Übergabe von Initialisierungsdaten erzeugt werden. |
|
Einem Circle-Objekt kann bei der Instanziierung sein Radius übergeben werden. |
|
Bei der Instanziierung kann sowohl der Radius als auch die Lage des Mittelpunktes festgelegt werden. |
Der Code der Circle-Klasse kann zur Erfüllung der Forderungen wie folgt ergänzt werden:
| // ---------- Konstruktoren (vorläufig) ----------
|
| public Circle() {}
|
| public Circle(double Radius) {
|
| radius = Radius;
|
| }
|
| public Circle(double Radius, int X, int Y) {
|
| xKoordinate = X;
|
| yKoordinate = Y;
|
| radius = Radius;
|
| }
|
Den beiden parametrisierten Konstruktoren wird als Argument der Radius des zu konstruierenden Objekts übergeben. Dabei wurde noch nicht berücksichtigt, dass der Benutzer einen dieser Konstruktoren mit einem unzulässigen negativen Wert aufruft. Die Anweisung
| meinKreis = new Circle(-2, 4, 7);
|
könnte daher unsere selbst auferlegte Randbedingung nicht erfüllen, da sich der übergebene Radius ungeprüft in das Feld radius schreiben wird. Ein denkbarer Lösungsansatz dieses Problems wäre, in den beiden parametrisierten Konstruktoren die Gültigkeit mit einer bedingten Anweisung zu prüfen. Das Ziel wäre damit zwar erreicht, aber auf wenig elegante Art und Weise.
Nahe liegender ist es, die Eigenschaftsmethode zu benutzen, in der bereits die entsprechende Prüfung codiert ist. Dabei ist nur noch ein kleines Hindernis zu überwinden: In den Konstruktoren überdeckt der lokale Parameter Radius die gleichnamige Eigenschaftsmethode. Um Letztere aufzurufen, muss der Aufruf unter Angabe der this-Referenz erfolgen:
Da es vorstellbar ist, dass auch die Lage eines Kreises auf bestimmte Koordinaten beschränkt werden muss, sollte auch die Wertzuweisung an die Mittelpunktskoordinaten über die entsprechenden Eigenschaftsmethoden führen.
Nun können wir die Konstruktoren so ändern, dass sie unseren Anforderungen genügen:
| // ---------- Konstruktoren ----------
|
| public Circle() {}
|
| public Circle(double Radius) {
|
| this.Radius = Radius;
|
| }
|
| public Circle(double Radius, int X, int Y) {
|
| this.XKoordinate = X;
|
| this.YKoordinate = Y;
|
| this.Radius = Radius;
|
| }
|
4.5.2 Die Konstruktoraufrufe
 
Im Allgemeinen werden Konstruktoren dazu benutzt, den Feldern eines Objekts bestimmte Startwerte zuzuweisen. Um ein Circle-Objekt zu erzeugen, stehen mit der obigen Konstruktorüberladung drei Möglichkeiten zur Verfügung:
|
Es wird ein Kreis ohne die Übergabe eines Arguments erzeugt. Dabei wird der parameterlose Konstruktor aufgerufen: |
Circle meinKreis = new Circle();
|
|
Der Kreis hat in diesem Fall den Radius 0, die Mittelpunktskoordinaten werden ebenfalls mit 0 initialisiert. |
|
|
|
|
Ein neuer Kreis wird nur mit dem Radius definiert, z.B.: |
Circle meinKreis = new Circle(10);
|
|
Es wird der Konstruktor aufgerufen, der ein Argument entgegennehmen kann. Da den Mittelpunktskoordinaten kein expliziter Wert zugewiesen wird, enthalten die entsprechenden Instanzvariablen xKoordinate und yKoordinate nur die standardmäßigen Initialisierungswerte 0. |
|
|
|
|
Einem Kreis werden bei der Erzeugung sowohl der Radius (im Beispiel unten 10) als auch die Mittelpunktskoordinaten (im Beispiel unten x = 15 und y = 20) übergeben. |
Circle meinKreis = new Circle(10, 15, 20);
Bei der Instanziierung einer Klasse muss der C#-Compiler selbst herausfinden, welcher Konstruktor aufgerufen werden soll. Dazu werden die Typen der übergebenen Argumente mit denen der Konstruktoren verglichen. Liegt Doppeldeutigkeit vor oder können die Argumente nicht zugeordnet werden, löst der Compiler einen Fehler aus.
4.5.3 Definition von Konstruktoren
 
Trotz der Ähnlichkeit zwischen Konstruktoren und Methoden unterliegen Konstruktoren bestimmten, teilweise auch abweichenden Regeln:
|
Die Bezeichner der Konstruktoren einer Klasse sind gleich lautend mit dem Klassenbezeichner. |
|
Konstruktoren haben grundsätzlich keinen Rückgabewert, auch nicht void. |
|
Die Parameterliste eines Konstruktors ist beliebig. |
|
Der Konstruktor einer Klasse wird bei der Instanziierung mit dem Schlüsselwort new aufgerufen. |
|
Ein Konstruktor kann nicht auf ein bereits bestehendes Objekt aufgerufen werden, beispielsweise um Instanzvariablen andere Werte zuzuweisen. |
Entwickeln Sie in eine konstruktorlose Klasse, wird bei der Erzeugung eines Objekts ein impliziter, parameterloser Standardkonstruktor aufgerufen. Nun folgt noch eine weitere sehr wichtige Regel:
| Der implizite Standardkonstruktor existiert nur dann, wenn er nicht durch einen parameterlosen Konstruktor explizit überschrieben oder durch einen parametrisierten überladen wird.
|
Implementieren Sie nur einen einzigen parametrisierten Konstruktor, enthält die Klasse keinen impliziten Standardkonstruktor mehr. Wird er dennoch benötigt, muss er codiert werden – so wie im Beispiel der Klasse Circle. Ohne den parameterlosen Konstruktor ist es nicht möglich, ein Objekt dieser Klasse ohne die gleichzeitige Übergabe einer Argumentenliste zu erzeugen. Aus diesem Grund haben wir auch in Circle einen parameterlosen Konstruktor definiert, obwohl er keinen Code enthält. Wir entsprechen damit unserer selbst auferlegten Forderung nach der Instanziierung eines Circle-Objekts ohne Parameterübergabe.
Eine herausragende Bedeutung spielt der parameterlose Konstruktor in der Vererbung. Nehmen Sie die Aussage, dass in jeder vererbungsfähigen Klasse ein parameterloser Konstruktor definiert werden sollte, derzeit noch als eine notwendige Tatsache hin. Wir werden dieses Thema in Kapitel 6 noch einmal aufgreifen.
4.5.4 »internal«-Konstruktoren
 
public deklarierte Konstruktoren stehen allen Benutzern der Klasse zur Verfügung. Eine fremde Anwendung kann die öffentlichen Konstruktoren dazu benutzen, ein Objekt nach den Maßstäben, die in der Parameterliste des Konstruktors festgelegt sind, zu erzeugen.
Unter Umständen ist es wünschenswert, eine Klasse nicht allen Anwendungen offen zu legen und den Zugriff nur auf die aktuelle Assembly zu beschränken. Mit dem Zugriffsmodifizierer internal können Sie eine solche Einschränkung erzwingen. Dabei ist gegebenenfalls auch daran zu denken, dass der implizite Standardkonstruktor grundsätzlich immer öffentlich (public) ist.
4.5.5 »private«-Konstruktoren
 
Sie werden immer wieder Klassen entwickeln, die nicht instanziiert werden dürfen. Dazu gehören zum Beispiel Klassen, die ausnahmslos statische Methoden zur Verfügung stellen, die unter der Angabe des Klassennamens und nicht auf eine Objektreferenz aufgerufen werden.
Um die Instanziierung zu unterbinden, muss der parameterlose Konstruktor, der standardmäßig öffentlich ist, mit einem private-Zugriffsmodifizierer überschrieben werden.
| class MathLib {
|
| private MathLib() {/*...*/}
|
| ...
|
| }
|
Damit wird verhindert, dass der Compiler den öffentlichen Standardkonstruktor generiert, und die Klasse MathLib ist nicht instanziierbar. Parametrisierte Konstruktoren in nicht instanziierbaren Klassen zu definieren, macht keinen Sinn.
| Hinweis An dieser Stelle Stelle sollte ergänzend noch ein Hinweis gegeben werden. Sie können die Instanziierung von Klassen, die nur statische Elemente enthalten, auch unterbinden, wenn Sie die Klassendefinition durch den Modifizierer static ergänzen. In Kapitel 5 kommen wir noch einmal auf dieses Thema zu sprechen.
|
4.5.6 Konstruktorverkettung
 
Konstruktoren können wie Methoden überladen werden. Jeder Konstruktor enthält dabei typischerweise eine aufgrund der ihm übergebenen Argumente spezifische Implementierung. Manchmal kommt es vor, dass der Konstruktor einer Klasse Programmcode enthält, der von einem zweiten Konstruktor ebenfalls implementiert werden muss. Sehen wir uns dazu die beiden parametrisierten Konstruktoren der Klasse Circle an:
| public Circle(double Radius) {
|
| this.Radius = Radius;
|
| }
|
| public Circle(double Radius, int X, int Y) {
|
| this.XKoordinate = X;
|
| this.YKoordinate = Y;
|
| this.Radius = Radius;
|
| }
|
Es fällt auf, dass in beiden der Radius des Circle-Objekts festgelegt wird. Es liegt nahe, zur Vereinfachung den einparametrigen Konstruktor aus dem dreiparametrigen heraus aufzurufen.
Da Konstruktoren eine besondere Spielart der Methoden darstellen und über den Operator new aufgerufen werden, stellt C# eine syntaktische Variante bereit, mit der aus einem Konstruktor heraus ein anderer Konstruktor derselben Klasse ausgeführt wird. Hier kommt erneut die Referenz this ins Spiel, die Sie bereits weiter oben kennen gelernt haben.
| public Circle(double Radius) {
|
| this.Radius = Radius;
|
| }
|
| public Circle(double Radius, int X, int Y) : this(Radius) {
|
| this.XKoordinate = X;
|
| this.YKoordinate = Y;
|
| }
|
Die Signatur des dreiparametrigen Konstruktors ist mit
ergänzt worden. Dies bewirkt den Aufruf eines anderen Konstruktors, in unserem Fall den des einparametrigen der Klasse Circle. Gleichzeitig wird der vom Aufrufer übergebene Radius, den der dreiparametrige Konstruktor in seiner Parameterliste entgegennimmt, weitergeleitet. Der implizit aufgerufene einparametrige Konstruktor wird ausgeführt und gibt die Kontrolle danach an den aufrufenden zurück.
Eine Konstruktoraufrufverkettung darf auch über mehrere Konstruktoren hinweg erfolgen und muss nicht zwangsläufig in Richtung zu den Konstruktoren mit einer geringeren Anzahl von Parametern erfolgen. Es kann durchaus ein zweiparametriger Konstruktor den drei- oder vierparametrigen derselben Klasse aufrufen.
4.5.7 Zusammenfassung
 
|
Konstruktoren sind Methoden, die aufgerufen werden, sobald eine Klasse instanziiert wird. Sie dienen der Initialisierung des Objekts. |
|
In C# werden Konstruktoren in den Klassen als Routinen mit dem Typbezeichner implementiert. Konstruktoren haben keinen Rückgabewert, auch nicht void. |
|
Wird in einer Klassendefinition nicht explizit ein Konstruktor definiert, implementiert das System standardmäßig einen parameterlosen, um eine einfache Instanziierung zu gewährleisten. |
|
Der implizite Standardkonstruktor existiert nur dann, wenn er nicht durch andere, parametrisierte Konstruktoren überladen wird. Enthält eine Klasse parametrisierte Konstruktoren und wird gleichzeitig ein parameterloser benötigt, muss er explizit definiert werden. Jede Klasse, aus der weitere Klassen abgeleitet werden können, sollte einen parameterlosen Konstruktor enthalten. |
|
Ein Konstruktor kann nur durch die Zugriffsmodifizierer public, private und internal beschrieben werden. Wird der parameterlose Konstruktor private überschrieben, ohne einen weiteren öffentlich zu definieren, ist die Klasse nicht instanziierbar. |
|
Um aus einem Konstruktor heraus einen anderen desselben Objekts aufzurufen, wird hinter der Parameterliste die this-Referenz angegeben. Die Anzahl und die Typen der an this übergebenen Argumente bewirken die Ausführung eines bestimmten Konstruktors. |
|